package cn.momoky.ccool.service;

import cn.momoky.ccool.exception.FileNameRepeatException;
import cn.momoky.ccool.mapper.FileNodeMapper;
import cn.momoky.ccool.pojo.FileNode;
import cn.momoky.ccool.utils.FileTool;
import cn.momoky.ccool.utils.UUID;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@AllArgsConstructor
public class FileNodeService {

    private final FileTool fileTool;
    private final UUID uuid;
    private final FileNodeMapper fileNodeMapper;
    private final FileService fileService;
    private final FileMD5Service fileMD5Service;

    public FileNode mkdir(FileNode fileNode) {

        fileNode.setIsDir(true)
                .setState(0)
                .setCreateTime(System.currentTimeMillis())
                .setLastModified(fileNode.getCreateTime());

        createNode(fileNode);

        return fileNode;
    }

    public void newFileTree(Long parentId, List<FileNode> nodeList) {

        FileNode parent = this.findById(parentId);

        // 文件夹的根节点
        FileNode root = new FileNode();
        // 文件夹路径与文件节点的映射
        Map<String, FileNode> map = null;

        // 构建文件树
        for (FileNode n : nodeList) {
            String path = n.getPath();
            String dir = fileTool.getParentPath(path);

            if (map == null) {
                map = new HashMap<>();
                FileNode fileNode = new FileNode();
                fileNode.setId(uuid.get())
                        .setName(dir)
                        .setIsDir(true)
                        .setPath(parent.getPath() + "/" + dir)
                        .setCreateTime(System.currentTimeMillis())
                        .setLastModified(fileNode.getCreateTime())
                        .setParentId(parent.getId())
                        .setState(0);
                map.put(dir, fileNode);
                root = fileNode;
                n.setParentId(fileNode.getId());
                continue;
            }

            FileNode node = map.get(dir);
            if (node == null) {
                String[] nameList = dir.split("/");
                int lev = nameList.length;
                while (node == null) {
                    dir = fileTool.getParentPath(dir);
                    node = map.get(dir);
                    lev--;
                }
                List<FileNode> children = node.getChildren();
                for (int i = lev;i < nameList.length;i++) {
                    FileNode fileNode = new FileNode();
                    fileNode.setId(uuid.get())
                            .setName(nameList[i])
                            .setIsDir(true)
                            .setPath(node.getPath() + "/" + nameList[i])
                            .setCreateTime(System.currentTimeMillis())
                            .setLastModified(fileNode.getCreateTime())
                            .setParentId(node.getId())
                            .setState(0);
                    children.add(fileNode);
                    node = fileNode;
                    children = fileNode.getChildren();
                    dir = dir + "/" + nameList[i];
                    map.put(dir, fileNode);
                }
            }
            n.setParentId(node.getId());
        }

        List<FileNode> folderList = new ArrayList<>();

        // 前序遍历文件树，设置节点left和right
        int p = parent.getRight();
        Stack<FileNode> stack = new Stack<>();
        // 记录已经访问过的文件节点
        Set<Long> set = new HashSet<>();
        stack.push(root);
        while (!stack.empty()) {
            FileNode node = stack.pop();
            if (set.contains(node.getId())) {
                node.setRight(p);
                folderList.add(node);
            } else {
                node.setLeft(p);
                stack.push(node);
                List<FileNode> children = node.getChildren();
                for (int i = children.size() - 1;i >= 0;i--) {
                    stack.push(children.get(i));
                }
                set.add(node.getId());
            }
            System.out.println(node.getPath());
            p++;
        }

        int step = p - parent.getRight();
        this.incrLeftByLeftAfter(parent.getRight(), step);
        this.incrRightByRightAfter(parent.getRight() - 1, step);
        fileNodeMapper.addAll(folderList);
    }

    public void createNode(FileNode fileNode) {
        FileNode f = this.findByNameAndParentId(
                fileNode.getName(),
                fileNode.getParentId()
        );
        if (f != null) throw new FileNameRepeatException();

        FileNode parent = this.findById(fileNode.getParentId());

        int left = parent.getRight();
        int right = left + 1;
        long parentId = fileNode.getParentId();
        String path = parent.getPath() + "/" + fileNode.getName();

        this.incrLeftByLeftAfter(parent.getRight(), 2);
        this.incrRightByRightAfter(parent.getRight() - 1, 2);

        fileNode.setId(uuid.get())
                .setPath(path)
                .setParentId(parentId)
                .setLeft(left)
                .setRight(right);

        fileNodeMapper.add(fileNode);

    }

    public void save(FileNode fileNode) {
        fileNodeMapper.insert(fileNode);
    }

    public void saveAll(Collection<FileNode> fns) {
        /*fileNodeMapper*/
    }

    public void removeNode(Long id) {
        FileNode fileNode = findById(id);
        if (fileNode.getRight() - fileNode.getLeft() == 1) {
            if (!fileNode.getIsDir()) {
                String md5 = fileMD5Service.getMD5ByNodeId(fileNode.getId());
                fileService.del(md5);
                fileMD5Service.delByNodeId(fileNode.getId());
            }
            this.delById(id);
            this.incrRightById(fileNode.getParentId(), -2);
            return;
        }

        List<FileNode> nodeList = fileNodeMapper.findByLeftIn(fileNode.getLeft() + 1, fileNode.getRight() - 1);

        List<Long> nodeIds = nodeList
                .stream()
                .filter(fn -> !fn.getIsDir())
                .map(FileNode::getId)
                .collect(Collectors.toList());

        if (!nodeIds.isEmpty()) {
            List<String> md5List = fileMD5Service.getMD5ByNodeIds(nodeIds);
            fileService.del(md5List);
            fileMD5Service.delByNodeIds(nodeIds);
        }

        this.delByLeftIn(fileNode.getLeft() + 1, fileNode.getRight() - 1);
        this.delById(id);
        this.incrRightById(fileNode.getParentId(), -((nodeList.size() + 1) * 2));
    }

    public void removeNode(List<Long> ids) {
        ids.forEach(this::removeNode);
    }


    public void delById(Long id) {
        fileNodeMapper.deleteById(id);
    }

    public void delByLeftIn(Integer start, Integer end) {
        fileNodeMapper.delete(
                new QueryWrapper<FileNode>()
                .between("`left`", start, end)
        );
    }

    public FileNode findById(Long id) {
        return fileNodeMapper.selectById(id);
    }

    public List<FileNode> findByParentId(Long parentId) {
        return fileNodeMapper.selectList(
                new QueryWrapper<FileNode>()
                .eq("parent_id", parentId)
        );
    }

    public FileNode findByPath(String path) {
        return fileNodeMapper.selectOne(
                new QueryWrapper<FileNode>()
                .eq("path", path)
        );
    }

    public FileNode findByNameAndParentId(String name, Long parentId) {
        return fileNodeMapper.selectOne(
                new QueryWrapper<FileNode>()
                .eq("name", name)
                .eq("parent_id", parentId)
        );
    }

    public FileNode getRoot() {
        return fileNodeMapper.selectOne(
                new QueryWrapper<FileNode>()
                .isNull("parent_id")
        );
    }

    public void incrLeftById(Long id, Integer step) {
        fileNodeMapper.incrLeftById(id, step);
    }

    public void incrRightById(Long id, Integer step) {
        fileNodeMapper.incrRightById(id, step);
    }

    public void incrLeftByLeftAfter(Integer left, Integer step) {
        fileNodeMapper.incrLeftByLeftAfter(left, step);
    }

    public void incrRightByRightAfter(Integer right, Integer step) {
        fileNodeMapper.incrRightByRightAfter(right, step);
    }

    public void updateState(Long id, Integer state) {
        fileNodeMapper.updateState(id, state);
    }
}
